// file:arch/x86/include/arch/page.h
// autor:jiang xinpeng
// time:2020.12.25
// update:2021.3.5
// copyright:(C) 2020-2050 by jiang xinpeng,All right are reserved.

#ifndef X86_PAGE_H
#define X86_PAGE_H

#include <arch/config.h>
#include <arch/pymem.h>
#include <arch/interrupt.h>
#include <arch/x86.h>
#include <arch/mem_pool.h>
#include <lib/stddef.h>
#include <os/debug.h>

// config page size
#ifdef CONFIG_PAGE_4MB
#define PAGE_4MB
#else
#define PAGE_4KB
#endif

// page size
#ifdef PAGE_4MB
#define PAGE_SIZE 0x400000
#else
#define PAGE_SIZE 0x1000
#endif

// boot map page table num
#define PAGETABLE_HAD_MAP 5

// page high area base
#define PAGE_HIGH_BASE 0x80000000
// kernel vbase
#define KERNEL_VMM_BASE PAGE_HIGH_BASE

// there are kernel global PDT pybase
#define PAGEDIR_PYBASE 0x8F2000
#define PAGEDIR_VBASE (KERNEL_VMM_BASE + PAGEDIR_PYBASE)
// define page table base
#define PAGETABLE_PYBASE 0x8F3000
#define PAGETABLE_VBASE (KERNEL_VMM_BASE + PAGETABLE_PYBASE)

// page descript size
#define PDE_DESCRIPT_SIZE 4

// define tern size
#define PDE_SIZE 4
#define PTE_SIZE 4

// indiate page attribute
#define PAGE_4MBPAGE 0x40
#define PAGE_ACCESS 0x20
#define PAGE_PCD 0x10
#define PAGE_PWT 0x8
#define PAGE_USERPAGE 0x4
#define PAGE_WRITE 0x2
#define PAGE_PRESENT 0x1

// kernel page attr
#define KERNEL_PAGE_ATTR (PAGE_PRESENT | PAGE_WRITE)
#define USER_PAGE_ATTR (PAGE_PRESENT | PAGE_WRITE | PAGE_USERPAGE)

// page protect flags
#define PROTE_NONE 0x01   // page no can access
#define PROTE_REMAP 0x02  // page remap
#define PROTE_WRITE 0x04  // page can write
#define PROTE_READ 0x08   // page can read
#define PROTE_EXEC 0x10   // page can exec
#define PROTE_KERNEL 0x20 // page in kernel
#define PROTE_USER 0x40   // page in user

// page fault
#define PAGE_ERR_NOPRESENT 0x0
#define PAGE_ERR_PROTECT 0x1
#define PAGE_ERR_READ 0x2
#define PAGE_ERR_WRITE 0x4
#define PAGE_ERR_SUPERVISOR 0x8
#define PAGE_ERR_USER 0x10

// get PDE index
#define PDE_INDEX(address) ((address & 0xffc00000) >> 22)
// get PTE index
#define PTE_INDEX(address) ((address & 0x003ff000) >> 12)
// get page offset
#define PAGE_OFF(address) (address & 0x00000fff)

// get pagebase
#define PAGEBASE_MASK (~PAGE_LIMIT)

#define PAGE_LIMIT (PAGE_SIZE - 1)
#define PAGEALIGN_MASK (~(PAGE_SIZE - 1)) //address align to page bound
#define PAGE_ALIGN(base) ((base+PAGE_LIMIT)&PAGEALIGN_MASK) //adress up align to page size

// enable page feature
#define ENABLE_PAGE (1 << 31)

// how much pte in pdt
#define PTE_MAX 1024

// kenrel vbase and pybase map relationship
#define KERNEL_PYBASE2VBASE(py) ((void *)((address_t)py + PAGE_HIGH_BASE))
#define KERNEL_VBASE2PYBASE(v) ((void *)((address_t)v - PAGE_HIGH_BASE))

typedef uint32_t pde_t;
typedef uint32_t pte_t;

static void *GetPdtPybase()
{
        return (void *)PAGEDIR_PYBASE;
}

static void *GetPtePybase()
{
        return (void *)PAGETABLE_PYBASE;
}

static pde_t *GetPdeVptr(uint32_t linebase)
{
        // access pdt as a page,pde as a page off
        pde_t *pde = ((void *)(0xfffff000 + PDE_INDEX(linebase) * PDE_SIZE));

        // KPrint("[page] linebase %x pde base %x\n", linebase, pde);
        return pde;
}

static pte_t *GetPteVptr(uint32_t linebase)
{
        // access pdt as pt,pt as page,pte as a page off
        pte_t *pte = (void *)(0xffc00000 + ((linebase & 0xffc00000) >> 10) + PTE_INDEX(linebase) * PTE_SIZE);

        // KPrint("[page] linebase %x pte base %x\n", linebase, pte);
        return pte;
}

static uint32_t Vbase2Pybase(uint32_t vbase)
{
        pte_t *pte = GetPteVptr(vbase);
        uint32_t pybase = *pte & PAGE_PRESENT ? (*pte & PAGEBASE_MASK) + (vbase & 0x00000fff) : -1; // if pte present,pybase=page base,or pybase=-1

        return pybase;
}

extern uint8_t page_enable; // indiate the page feature is enalbe

// function
int _VbaseLinkPybaseUnsafe(uint32_t vbase, uint32_t pybase, uint32_t attribute);
int _VbaseLinkPybaseSafe(uint32_t vbase, uint32_t pybase, uint32_t attribute);
void _VbaseUnlinkPybase(uint32_t vbase);
int _MapPage(uint32_t startbase, uint32_t len, uint32_t protect);
void _MapFixedPage(uint32_t vbase, uint32_t pybase, uint32_t len, uint32_t protect);
void _UnMapPage(uint32_t startbase, uint32_t len, uint32_t fix);
int CheckPageRead(uint32_t vbase, uint32_t len);
int CheckPageWrite(uint32_t vbase, uint32_t len);
int PageDoFault(trap_frame_t *frame);
void SetupPage();
int CheckPagePresent(uint64_t vaddr);

#define VbaseLinkPybaseSafe(vbase, pybase, attr) _VbaseLinkPybaseSafe(vbase, pybase, attr) // link vbase and pybase
#define VbaseLinkPybase(vbase, pybase, attr) _VbaseLinkPybaseUnsafe(vbase, pybase, attr)     // link vbase and pybase
#define VbaseUnlinkPybase(vbase) _VbaseUnlinkPybase(vbase)                                   // unlink vbase and pybase
#define AllocKernelPage(count) AllocMemNode(MEM_MODE_KERNEL, count)                          // alloc page
#define AllocUserPage(count) AllocMemNode(MEM_MODE_USER, count)                              // alloc page
#define AllocDmaPage(count) AllocMemNode(MEM_NODE_DMA, count)
#define FreePage(page) FreeMemNode(page)                                             // free page
#define FlushTBL(vbase) X86Invlpg(vbase)                                             // update tbl
#define VMemMap(vbase, len, attr) _MapPage(vbase, len, attr)                         // create vmem area map to pymem
#define VUnMemMap(vbase, len, fix) _UnMapPage(vbase, len, fix)                       // del vmem area map
#define VMemMapFix(vbase, pybase, len, attr) _MapFixedPage(vbase, pybase, len, attr) // map fixed page
#define GetFreePageCount _GetFreePageCount()                                         // get free page count
#define PageAlign(base) ((base + PAGE_LIMIT) & PAGEALIGN_MASK)                       // align base to page size
#define KernelPageCopyTo KernelPageDirCopyTo()                                       // create user page dir and copy kernel page

#endif